using System;
using System.Collections.Generic;
using System.Text;
using Pegasus.Diagnostics;
using Pegasus.Log4Net;
using System.Diagnostics;
using Pegasus.Log4Net.Core;

namespace Pegasus
{
    /// <summary>
    /// Abstract class containing utility methods to repeat a method
    /// </summary>
    public abstract class MethodRepeater
    {
        /// <summary>
        /// Delegate method that is repeated.  It returns an object which is a class.
        /// </summary>
        public delegate object RepeatDelegate();

        /// <summary>
        /// A delegate method that is repeated without any return value.
        /// </summary>
        public delegate void VoidDelegate();

        /// <summary>
        /// Same as the method with a LogExceptionDelegate except that the exceptions are not logged.
        /// </summary>
        /// <param name="methodToRepeat">The method to repeat.</param>
        /// <param name="numTries">The num tries.</param>
        public static void RepeatMethod( VoidDelegate methodToRepeat, int numTries )
        {
            RepeatMethod( methodToRepeat, null, Level.Off, numTries );
        }

        /// <summary>
        /// Tries to call methodToRepeat repeatedly.  Throws last recieved exception when all attempts fail.
        /// </summary>
        /// <param name="methodToRepeat">The method to repeat.</param>
        /// <param name="logger">The logging mechanism for retry attempts.</param>
        /// <param name="level">The level of logging.</param>
        /// <param name="numTries">The number of tries.</param>
        public static void RepeatMethod( VoidDelegate methodToRepeat, ILog logger, Level level, int numTries )
        {
            ParamCode.AssertRange( numTries, 1, Int32.MaxValue, "numTries" );

            int count = 0;
            while( true )
            {
                try
                {
                    methodToRepeat();
                    break;
                }
                catch( Exception ex )
                {
                    count++;

                    if( count >= numTries )
                    {
                        throw;
                    }
                    else
                    {
                        if( logger != null )
                        {
                            StackFrame frame = new StackFrame( true );
                            logger.Logger.Log( frame.GetMethod().DeclaringType,
                                level, string.Format( "Try number: {0}", count ), ex );
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Same as the method with a LogExceptionDelegate except that the exceptions are not logged.
        /// </summary>
        /// <param name="methodToRepeat">The method to repeat.</param>
        /// <param name="numTries">The num tries.</param>
        /// <returns>The return of methodToRepeat casted to the retType type.</returns>
        public static retType RepeatMethod<retType>( RepeatDelegate methodToRepeat, int numTries ) where retType : class
        {
            return RepeatMethod<retType>( methodToRepeat, null, Level.Off, numTries );
        }

        /// <summary>
        /// Tries to call methodToRepeat repeatedly.  Throws last recieved exception when all attempts fail.
        /// </summary>
        /// <param name="methodToRepeat">The method to repeat.</param>
        /// <param name="logger">The method to log any exceptions that occur.</param>
        /// <param name="level">The level.</param>
        /// <param name="numTries">The number of tries.</param>
        /// <returns>
        /// The return of methodToRepeat casted to the retType type.
        /// </returns>
        public static retType RepeatMethod<retType>( RepeatDelegate methodToRepeat, ILog logger, Level level, int numTries ) where retType : class
        {
            ParamCode.AssertRange( numTries, 1, Int32.MaxValue, "numTries" );

            int count = 0;
            while( true )
            {
                try
                {
                    return methodToRepeat() as retType;
                }
                catch( Exception ex )
                {
                    count++;

                    if( count >= numTries )
                    {
                        throw;
                    }
                    else
                    {
                        if( logger != null )
                        {
                            if( logger != null )
                            {
                                StackFrame frame = new StackFrame( true );
                                logger.Logger.Log( frame.GetMethod().DeclaringType,
                                    level, string.Format( "Try number: {0}", count ), ex );
                            }
                        }
                    }
                }
            }
        }
    }
}
